package uk.ac.aber.paws.client.idlelogger;

import uk.ac.aber.paws.client.config.ConfigManager;
import uk.ac.aber.paws.client.constants.Constants;
import uk.ac.aber.paws.client.net.SendMessage;
import uk.ac.aber.paws.client.system.*;
import uk.ac.aber.paws.client.idlelogger.gui.PAWSSysTray;
import uk.ac.aber.paws.client.idlelogger.languages.Languages;
import uk.ac.aber.paws.client.util.*;
import static uk.ac.aber.paws.client.constants.CommandConstants.*;

import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.*;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import org.apache.commons.lang.ArrayUtils;

import com.apple.eawt.Application;

/**
 * @author Rob Johnson, Aberystwyth University
 * @email rob.johnson@aber.ac.uk
 * @date 8th August 2011
 * 
 * IdleLogger.java
 * 
 * This is the main class for running the Idle Logger. The IdleLogger is responsible for
 * 
 * a) submitting idle time info to the PAWS Client
 * b) providing a SysTray facility for launching of information windows and other
 *    applications
 * c) Monitors the status of PAWS Client service and invokes auto service starting
 *    if the PAWS Client service appears to be down
 *    
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class IdleLogger {

	/**
	 * Records the timestamp for the last successful idle log procedure
	 */
	private static long lastSuccess = 0l;
	
	/**
	 * Records the timestamp for the last failed idle log procedure
	 */
	private static long lastProblem = 0l;
	
	/**
	 * Records the current error mode, false indicates that it cannot log to the 
	 * PAWS Client service
	 */
	private static boolean errorMode = false;
	
	/**
	 * Stores the session state information in relation to the state of the active
	 * PAWS Client service
	 */
	private static Hashtable<String,Object> serviceInfo;
	
	/**
	 * A flag to indicate if the service is in a critical state. True indicates the 
	 * PAWS CLient service cannot be started, false is IdleLogger is attempting to 
	 * start the PAWS Client service. This flag is only relevant if errorMode is true.
	 */
	private static boolean serviceIsCritical = false;
	
	public static String RERUN_COMMAND;
	
	/**
	 * Stores all configuration for this application
	 */
	private static ConfigManager mainConfig;
	
	/**
	 * Stores the active language pack for this application
	 */
	private static ConfigManager languagePack;
	
	/**
	 * Stores the application arguments, passed in my the parent Initiator class
	 */
	private static String programArgs[];
	
	private static boolean disablePAWS = false;
	
	/**
	 * Stores the Operating System name
	 */
	private static String OS = null;
	
	private static long clientLastStarted = 0;
	
	private static PAWSDisabler disabler = null;
	
	/**
	 * Updates the session state info
	 * @param info session state information
	 */
	public static void setInfo(Hashtable<String,Object> info) {
		
		serviceInfo = info;
	}
	
	public static PAWSDisabler getDisabler() {
		return disabler;
	}
	
	public static void setDisabler(PAWSDisabler disablerIn) {
		disabler = disablerIn;
	}
	
	/**
	 * Returns the current session state information
	 * @return session state information
	 */
	public static Hashtable<String,Object> getInfo() {
		 return serviceInfo;
	}
	
	public static void setDisablePAWS(boolean state) {
		disablePAWS = state;
	}
	
	public static boolean getDisablePAWS() {
		return disablePAWS;
	}
	
	/**
	 * Loads the required language pack
	 * @param lang Language in the form "en.lang" for example
	 */
	public static void loadLanguage(String lang) {
	
		languagePack = new ConfigManager("uk/ac/aber/paws/client/idlelogger/languages/"+lang,null,true);
		
	}

	public static void main (String args[]) {
	
		programArgs = args;
		
		try {
			
			// load configuration file - the path to this is expected as parameter 1
			String mainConfigPath = args[0];
						
			// load o/s specific file - the path to this is expected as parameter 2
			String osConfigPath = args[1];
						
			// load the config file
			mainConfig = new ConfigManager(mainConfigPath,osConfigPath,false);
		
			String updatesFlag = "UPDATESOFF";
			
			try {
				updatesFlag = programArgs[2];
			}
			catch(IndexOutOfBoundsException ex) {
			}
			
			RERUN_COMMAND = FileUtils.convertUnixToDOSPath("\""+IdleLogger.getMainConfig().getProperty("app.main.homedir")+IdleLogger.getMainConfig().getProperty("app.main.libdir")+IdleLogger.getMainConfig().getProperty("app.main.pawsclientname"))+"\" idlelogger \""+programArgs[0]+"\" \""+programArgs[1]+"\" "+updatesFlag;
			
			// get the operating system
			OS = System.getProperty("os.name").toUpperCase();
			
			System.out.println("Operating System detected as :"+OS);
			
			// load the language file
			loadLanguage(Languages.getSelectedLanguage());
			
			// launch the SysTray gui
			PAWSSysTray.launchSysTray();
			
			// recursively loop logging the idle time to the PAWS Client service
			while (true) {
			
				// get the last click event/idle time for the system
				long lastClick = -1;
				
				String username = "unknown";
				
				if (OS.equals(Constants.OS_FLAG_LINUX)) {
					lastClick = new LinuxIdleTime().getIdleTimeMillis();
					username = System.getenv("USERNAME");
					System.out.println("Last click (for Linux) ="+lastClick);
				}
				else if (OS.equals(Constants.OS_FLAG_MAC)) {
					lastClick = new MacIdleTime().getIdleTimeMillis();
					username = System.getenv("USER");
					
					ImageIcon ii = new ImageIcon(PAWSSysTray.class.getClassLoader().getResource("uk/ac/aber/paws/client/images/pawssmall.png"));
					
					Image image = ii.getImage();
					image = image.getScaledInstance(24, 24, Image.SCALE_SMOOTH);
				
					new Application().setDockIconImage(image);
	
					System.setProperty("com.apple.mrj.application.apple.menu.about.name", "PAWS Client");
					
					System.out.println("Last click (for MAC) ="+lastClick+" (user is "+username+")");
				}
				else {
					lastClick = NativeCalls.getIdleTimeMillisWin32();
					username = System.getenv("USERNAME");
					System.out.println("Last click (for Windows) ="+lastClick);
				}
				
				// should the sleep disable be set, override the lastClick value
				if (disablePAWS) {
					System.out.println("PAWS is disabled.");
					lastClick = 0;
				}
				
				// send the idle time info to PAWS Client
				boolean sendResult = SendMessage.sendIdleTimeMessage(lastClick,Integer.parseInt(mainConfig.getProperty("app.main.net.secureservercommsport")),username);		
				
				System.out.println("Report success: "+sendResult);
				
				// if cannot contact the PAWS Client
				if (!sendResult && !errorMode) {
				
					System.out.println("Cannot contact service - 1st instance");
					
					// launch the warning mode in the GUI
					if (!errorMode) {
						PAWSSysTray.warningMode();
					}
					
					errorMode = true;
					
					lastProblem = Calendar.getInstance().getTimeInMillis();
					
					// attempt to restart the PAWS Client service
					if (!serviceIsCritical) {
						
						if (OS.equals(Constants.OS_FLAG_LINUX)) {
							
						}
						else if (OS.equals(Constants.OS_FLAG_MAC)) {
							
						}
						else {
							new FixService().start();
						}
					}
				}
				// if in error mode still yet service is now obtainable, update interface
				else if (errorMode && sendResult) {
					
					System.out.println("Service now seems to be fixed following error");
					
					PAWSSysTray.normalMode();
					
					serviceIsCritical = false;
					errorMode = false;
					
					// check to see if PAWS Client service has been restarted recently, if so, restart IdleLogger
					Hashtable data = SendMessage.requestInformation(Integer.parseInt(IdleLogger.getMainConfig().getProperty("app.main.net.secureservercommsport")));
					
					if (data != null) {
						long lastStarted = (Long)data.get("PAWS_CLIENT_STARTIME");
						
						// if not the 1st check...
						if (clientLastStarted > 0) {
							
							// jar file has been updated - restart this app
							if (lastStarted != clientLastStarted) {
								
								try {
									restartApplication(null);
								} 
								catch (IOException e) {
									// TODO Auto-generated catch block
									System.out.println(e.toString());
									e.printStackTrace();
								}
						
							}
						}
						else {
							clientLastStarted = lastStarted;
						}
					}	
				}
				else if (!sendResult) {
					
				}
				// service appears to be running ok
				else {
					
					lastSuccess = Calendar.getInstance().getTimeInMillis();
					
					Hashtable data = SendMessage.requestInformation(Integer.parseInt(IdleLogger.getMainConfig().getProperty("app.main.net.secureservercommsport")));
					
					if (data != null) {
						clientLastStarted = (Long)data.get("PAWS_CLIENT_STARTIME");
					}
					
				}
				
				// sleep for a period until the next check
				Thread.sleep(1000*Integer.parseInt(IdleLogger.getMainConfig().getProperty("app.main.processinterrupts.idleloggerreport")));
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	/**
	 * Returns the main IdleLogger application configuration object to the caller
	 * @return
	 */
	public static ConfigManager getMainConfig() {
		return mainConfig;
	}
	
	/**
	 * Returns the object storing the language pack information to the caller
	 * @return Language pack properties
	 */
	public static ConfigManager getLanguagePack() {
		return languagePack;
	}
	
	public static void performManualRestart() {

		try {
			
			// stop the PAWS Client Service service
			ServiceOperations.switchService(false);
			
			// start the PAWS Client Service service
			ServiceOperations.switchService(true);
			
			// restart the IdleLogger
			restartApplication(null);
			
		} 
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Sets the service to critical which happens when the PAWS Client service down and
	 * cannot be restarted after a number of attempts. Usually signals a reboot is required.
	 */
	public static void setServiceToCritical() {
		serviceIsCritical = true;
	}
	
	/**
	 * Gets the critical status
	 * @return Returns true if service cannot be restarted or false if it is trying to restart the service
	 */
	public static boolean getServiceIsCritical() {
		return serviceIsCritical;
	}
	
	/**
	 * Returns the timestamp for the last success of the idle time being successfully registered with PAWS Client
	 * @return timestamp of last success
	 */
	public static long getLastSuccess() {
		return lastSuccess;
	}
	
	/**
	 * Returns the timestamp for the last identified problem with the PAWS Client Service
	 * @return timestamp of last problem
	 */
	public static long getLastProblem() {
		return lastProblem;
	}
	
	/**
	 * Returns the current error mode
	 * @return error mode
	 */
	public static boolean getErrorMode() {
		return errorMode;
	}
	
	/**
	 * Restart the current Java application
	 * @param runBeforeRestart some custom code to be run before restarting
	 * @throws IOException
	 */
	public static void restartApplication(Runnable runBeforeRestart) throws IOException {
		try {
			
			// java binary
			String java = System.getProperty("java.home") + "/bin/javaw";

			// vm arguments
			List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
			StringBuffer vmArgsOneLine = new StringBuffer();
			for (String arg : vmArguments) {
				// if it's the agent argument : we ignore it otherwise the
				// address of the old application and the new one will be in conflict
				if (!arg.contains("-agentlib")) {
					vmArgsOneLine.append(arg);
					vmArgsOneLine.append(" ");
				}
			}
			// init the command to execute, add the vm args
			final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);
			
			cmd.append("-jar ");

			cmd.append(" "+RERUN_COMMAND);
			
			// execute the command in a shutdown hook, to be sure that all the
			// resources have been disposed before restarting the application
			Runtime.getRuntime().addShutdownHook(new Thread() {
				@Override
				public void run() {
					try {
						Runtime.getRuntime().exec(cmd.toString());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
			// execute some custom code before restarting
			if (runBeforeRestart!= null) {
				runBeforeRestart.run();
			}
			// exit
			System.exit(0);
		}
		catch (Exception e) {
			// something went wrong
			throw new IOException("Error while trying to restart the application", e);
		}
	}
	
	public static String getOS() {
		return OS;
	}
}

